/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "log.h"
#include "dxinputsystem.h"
#include "dxrenderwindow.h"

#include <stdexcept>
#include <limits>

#ifdef max
#undef max
#endif

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{

#define INPUT_BUFFER_SIZE 64

	//helper class
	class DXRenderWindowInfoVisitor: public RenderWindowInfoVisitor
	{
	public:
		void Visit(DXRenderWindowInfo* info)
		{
			hWnd = info->GetHWND();
		};
		HWND hWnd;
	};

	/**********************************************************************************************************/
	InputSystem* DXInputSystemFactory::CreateInstance( const std::string& )
	{
		return new DXInputSystem();
	}
	/**********************************************************************************************************/
	DXInputSystem::DXInputSystem()
		:m_mouseX(0)
		,m_mouseDeltaX(0)
		,m_mouseY(0)
		,m_mouseDeltaY(0)
		,m_mouseWeel(0)
		,m_mouseWeelDelta(0)
		,m_screenWidth(std::numeric_limits<unsigned int>::max())
		,m_screenHeight(std::numeric_limits<unsigned int>::max())
		,m_isExclusive(true)
	{
	}
	/**********************************************************************************************************/
	void DXInputSystem::Init(RefPtr<RenderWindowInfo> param, bool exclusive)
	{
		m_isExclusive = exclusive;
		DXRenderWindowInfoVisitor visitor;
		param->Accept(visitor);
		m_hWnd = visitor.hWnd;

		InitLocal();
	}
	/**********************************************************************************************************/
	void DXInputSystem::InitLocal()
	{
		if(IsWindow(m_hWnd) == FALSE)
			throw std::invalid_argument("DXInputSystem wrong parent window");

		IDirectInput8* diObject = NULL;
		if(FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&diObject,NULL)))
			throw std::logic_error("Can't create direct input object.");

		m_diObject = ComPtr<IDirectInput8>(diObject);

		try
		{

			InitKeyboard();

			InitMouse();

			Log::GetSingleton() << "DX Input system is initialized" << std::endl;
		}
		catch(...)
		{
			Close();
			throw;
		}
	}
	/**********************************************************************************************************/
	void DXInputSystem::InitKeyboard()
	{

		IDirectInputDevice8* diDevice = NULL;
		if(FAILED(m_diObject->CreateDevice(GUID_SysKeyboard, &diDevice, NULL)))
			throw std::logic_error("Can't retrieve a pointer to an IDirectInputDevice8 interface for keyboard.");

		m_diKeyboard = ComPtr<IDirectInputDevice8>(diDevice);

		DWORD flags = 0;

		if(m_isExclusive)
			flags = DISCL_EXCLUSIVE | DISCL_FOREGROUND;
		else
			flags = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND;

		if(FAILED(m_diKeyboard->SetCooperativeLevel(m_hWnd, flags)))
			throw std::logic_error("Can't set cooperative level for keyboard.");

		if(FAILED(m_diKeyboard->SetDataFormat(&c_dfDIKeyboard)))
			throw std::logic_error("Can't set format for keyboard.");



		DIPROPDWORD KeyboardProp;
		KeyboardProp.diph.dwSize = sizeof (DIPROPDWORD);
		KeyboardProp.diph.dwHeaderSize = sizeof (DIPROPHEADER);
		KeyboardProp.diph.dwObj = 0;
		KeyboardProp.diph.dwHow = DIPH_DEVICE;
		KeyboardProp.dwData = INPUT_BUFFER_SIZE;
		if(FAILED(m_diKeyboard->SetProperty (DIPROP_BUFFERSIZE, &KeyboardProp.diph)))
			throw std::logic_error("Can't set buffer size for keyboard.");

		HRESULT hr = m_diKeyboard->Acquire();
		if(hr == DIERR_OTHERAPPHASPRIO)
		{
			while(hr == DIERR_OTHERAPPHASPRIO)
			{
				hr = m_diKeyboard->Acquire();
				Sleep(500);
			}
		}
		else if(hr != DI_OK)
			throw std::logic_error("Can't get access to the keyboard device.");
	}
	/**********************************************************************************************************/
	void DXInputSystem::InitMouse()
	{
		IDirectInputDevice8* diDevice = NULL;
		if(FAILED(m_diObject->CreateDevice(GUID_SysMouse, &diDevice, NULL)))
			throw std::logic_error("Can't retrieve a pointer to an IDirectInputDevice8 interface for mouse.");

		m_diMouse = ComPtr<IDirectInputDevice8>(diDevice);

		DWORD flags = 0;

		if(m_isExclusive)
			flags = DISCL_EXCLUSIVE | DISCL_FOREGROUND;
		else
			flags = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND;

		if(FAILED(m_diMouse->SetCooperativeLevel(m_hWnd, flags)))
			throw std::logic_error("Can't set cooperative level for mouse.");

		if(FAILED(m_diMouse->SetDataFormat(&c_dfDIMouse2)))
			throw std::logic_error("Can't set format for mouse.");

		DIPROPDWORD MouseProp;
		MouseProp.diph.dwSize = sizeof (DIPROPDWORD);
		MouseProp.diph.dwHeaderSize = sizeof (DIPROPHEADER);
		MouseProp.diph.dwObj = 0;
		MouseProp.diph.dwHow = DIPH_DEVICE;
		MouseProp.dwData = INPUT_BUFFER_SIZE;
		if(FAILED(m_diMouse->SetProperty (DIPROP_BUFFERSIZE, &MouseProp.diph)))
			throw std::logic_error("Can't set buffer size for mouse.");

		HRESULT hr = m_diMouse->Acquire();
		if(hr == DIERR_OTHERAPPHASPRIO)
		{
			while(hr == DIERR_OTHERAPPHASPRIO)
			{
				hr = m_diMouse->Acquire();
				Sleep(500);
			}
		}
		else if(hr != DI_OK)
			throw std::logic_error("Can't get access to the mouse device.");
	}
	/**********************************************************************************************************/
	void DXInputSystem::Close()
	{
		if(!m_diKeyboard.IsNull())
			m_diKeyboard->Unacquire();
		if(!m_diMouse.IsNull())
			m_diMouse->Unacquire();

		m_diMouse.Delete();
		m_diKeyboard.Delete();
		m_diObject.Delete();
	}
	/**********************************************************************************************************/
	DXInputSystem::~DXInputSystem()
	{
		Close();
	}
	/**********************************************************************************************************/
	void DXInputSystem::GetMousePosition( REAL* x_pos, REAL* y_pos )const
	{
		if(x_pos != 0 && y_pos != 0)
		{
			*x_pos = m_mouseX;
			*y_pos = m_mouseY;
		}
	}
	/**********************************************************************************************************/
	void DXInputSystem::Update()
	{
		HRESULT hr;
		//GET KEYBOARD
		DWORD keyboardObjectsCount = INPUT_BUFFER_SIZE;
		DIDEVICEOBJECTDATA keyboardObject[INPUT_BUFFER_SIZE];
		memset(keyboardObject,0,sizeof(DIDEVICEOBJECTDATA) * INPUT_BUFFER_SIZE);

		if(FAILED(hr = m_diKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), keyboardObject, &keyboardObjectsCount, 0)))
		{
			if ((hr == DIERR_NOTACQUIRED) || (hr == DIERR_INPUTLOST))
				m_diKeyboard->Acquire();

			return;
		}

		for (unsigned int i=0; i < keyboardObjectsCount; ++i)
		{
			if(keyboardObject[i].dwData & 0x80)
				KeyDownSignal(keyboardObject[i].dwOfs);
			else
			{
				KeyUpSignal(keyboardObject[i].dwOfs);


				HKL layout=GetKeyboardLayout(0);
				unsigned char state[256];
				if (GetKeyboardState(state) == TRUE)
				{
					UINT vk = MapVirtualKeyEx(keyboardObject[i].dwOfs,1,layout);
					unsigned short ch = 0;
					if(ToAsciiEx(vk,keyboardObject[i].dwOfs,state,&ch,0,layout) != 0)
					{
						CharPressedSignal(ch);
					}
				}
			}
		}

		//GET MOUSE
		DWORD mouseObjectsCount = INPUT_BUFFER_SIZE;
		DIDEVICEOBJECTDATA mouseObject[INPUT_BUFFER_SIZE];
		memset(mouseObject,0,sizeof(DIDEVICEOBJECTDATA) * INPUT_BUFFER_SIZE);


		if(FAILED(hr = m_diMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), mouseObject, &mouseObjectsCount, 0)))
		{
			if ((hr == DIERR_NOTACQUIRED) || (hr == DIERR_INPUTLOST))
				m_diMouse->Acquire();
			return;
		}

		// mouse move process
		m_mouseDeltaX = 0;
		m_mouseDeltaY = 0;
		m_mouseWeelDelta = 0;

		for(unsigned int i = 0; i < mouseObjectsCount; ++i)
		{
			if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_X))
			{
					m_mouseDeltaX = static_cast<int>(mouseObject[i].dwData * 2);
					if(m_mouseDeltaX != 0)
						MouseMoveSignal(m_mouseDeltaX, 0);
					if((m_mouseX + m_mouseDeltaX) < m_screenWidth)
						m_mouseX += m_mouseDeltaX;
			}
			else if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_Y))
			{
					m_mouseDeltaY = static_cast<int>(mouseObject[i].dwData * 2);
					if(m_mouseDeltaY != 0)
						MouseMoveSignal(0, m_mouseDeltaY);
					if((m_mouseY + m_mouseDeltaY) < m_screenHeight)
						m_mouseY += m_mouseDeltaY;
			}
			else if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_Z))
			{
					m_mouseWeelDelta = static_cast<int>(mouseObject[i].dwData * 2);
					if(m_mouseWeelDelta != 0)
						MouseWheelChangeSignal(m_mouseWeelDelta);
					m_mouseWeel += m_mouseWeelDelta;
			}
			else if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_BUTTON0)) //Left button
			{
					if(mouseObject[i].dwData & 0x80)
						MouseButtonDownSignal(InputSystem::LeftButton);
					else
						MouseButtonUpSignal(InputSystem::LeftButton);
			}
			else if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_BUTTON1)) //Right button
			{
					if(mouseObject[i].dwData & 0x80)
						MouseButtonDownSignal(InputSystem::RightButton);
					else
						MouseButtonUpSignal(InputSystem::RightButton);
			}
			else if(mouseObject[i].dwOfs == static_cast<DWORD>(DIMOFS_BUTTON2)) //Middle button
			{
					if(mouseObject[i].dwData & 0x80)
						MouseButtonDownSignal(InputSystem::MiddleButton);
					else
						MouseButtonUpSignal(InputSystem::MiddleButton);
			}
		}
	}
	/**********************************************************************************************************/
	void DXInputSystem::SetMouseArea(unsigned int width, unsigned int height)
	{
		m_screenWidth = width;
		m_screenHeight = height;
	}
	/**********************************************************************************************************/
	bool DXInputSystem::IsExclusive()
	{
		return m_isExclusive;
	}
	/**********************************************************************************************************/
	void DXInputSystem::ToggleExclusive(bool val)
	{
		if(m_isExclusive != val)
		{
			m_isExclusive = val;

			DWORD flags = 0;

			if(m_isExclusive)
				flags = DISCL_EXCLUSIVE | DISCL_FOREGROUND;
			else
				flags = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND;

			if(FAILED(m_diMouse->Unacquire()))
				throw std::logic_error("Can't Unacquire mouse.");
			if(FAILED(m_diMouse->SetCooperativeLevel(m_hWnd, flags)))
				throw std::logic_error("Can't set cooperative level for mouse.");

			if(FAILED(m_diKeyboard->Unacquire()))
				throw std::logic_error("Can't Unacquire keyboard.");
			if(FAILED(m_diKeyboard->SetCooperativeLevel(m_hWnd, flags)))
				throw std::logic_error("Can't set cooperative level for keyboard.");
		}
	}
	/**********************************************************************************************************/
}
